/**
 * @author wcj
 * @Date 2021/6/11 12:59
 * @Version 1.0
 * @Description 雪花算法：寓意为世界上没有两片雪花是相同的！
 * 用途：主要用于分布式系统生成用户的ID
 * 为什么：因为分布式系统在并发较大的是否，同一时间可能有多个用户访问，因此我们需要每个用户都有一个唯一性的标识，这正符合雪花算法的含义，即都不相同
 * 雪花算法的实现：生成的用户ID标识为一个长整型，即二进制为64位的数，其中第一位为符号位，后面的41表示时间戳，10位标识分布式服务器的机器号，12位为序列号。其中序列号的范围为0-4095（4096个数字）
 * 优点：①因为按照时间戳生成，所以用户ID分配呈现一个随时间递增的情形；②不重复，因此可以用于分布式
 * 缺点：①依赖服务器的时钟，这样的话可能出现时钟回拨导致用户ID重复的情况；②机器数量存在上限，即2^10 = 1024台；③机器如果宕机，存在机器ID的回收和分配问题
 */
public class SnowFlake {

    /**
     * 机器ID
     */
    private long workerId;

    /**
     * 机房ID
     */
    private long datacenterId;

    /**
     * 序列号
     */
    private long sequence;

    /**
     * 初始时间戳
     */
    private static final long INIT_TIMESTAMP = 2 ^ 41 - 1;

    /**
     * 机器ID占据的bit数
     */
    private static final long WORKER_BITS = 5L;

    /**
     * 机房ID占据的bit数
     */
    private static final long DATACENTER_BITS = 5L;

    /**
     * 序列号占据的bit数
     */
    private static final long SEQUENCE_BITS = 12L;

    /**
     * 时间戳偏移位数
     */
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_BITS + DATACENTER_BITS;

    /**
     * 机房ID偏移位数
     */
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_BITS;

    /**
     * 机器ID偏移位数
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 最大的序列号:4095
     */
    private static final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BITS);

    /**
     * 最大的机器号:2^5-1
     */
    private static final long MAX_WORKER = -1L ^ (-1L << WORKER_BITS);

    /**
     * 最大的机房号:2^5-1
     */
    private static final long MAX_DATACENTER = -1L ^ (-1L << DATACENTER_BITS);

    /**
     * 初始化的时间戳
     */
    private long lastTime = 0L;

    public SnowFlake(long workerId, long datacenterId, long sequence) {
        if (workerId > MAX_WORKER || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId is illegal: %d", workerId));
        }
        if (datacenterId > MAX_DATACENTER || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenterId is illegal: %d", datacenterId));
        }
        if (sequence > MAX_SEQUENCE || sequence < 0) {
            throw new IllegalArgumentException(String.format("sequence must between zero to 2^12-1，now is: %d", sequence));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    /**
     * 雪花算法的实现
     *
     * @return
     */
    public synchronized long nextId() {
        long current = currentMills();
        if (current < lastTime) {
            throw new RuntimeException(String.format("Clock move backwards，refuse generator id for %d mills", current));
        }
        if (current == lastTime) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                current = getNextMills(lastTime);
            }
        } else {
            sequence = 0L;
        }

        lastTime = current;

        return ((current - INIT_TIMESTAMP) << TIMESTAMP_SHIFT) | (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
    }

    /**
     * 当序列号溢出的时候，我们等待到下一毫秒
     *
     * @param lastTime
     * @return
     */
    private long getNextMills(long lastTime) {
        long current = currentMills();

        while (current <= lastTime) {
            current = currentMills();
        }

        return current;
    }

    /**
     * 获取当前时间戳
     *
     * @return
     */
    private long currentMills() {
        return System.currentTimeMillis();
    }

}
